﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace Noris.Tools.TraceVisualiser.Components
{
    /// <summary>
    /// Třída, která provádí míchání barev v jednorozměrném prostoru
    /// </summary>
    public class LinearColorBlender
    {
        public LinearColorBlender()
        {
            this._ColorPoints = new List<ColorBlendPoint>();
        }
        private List<ColorBlendPoint> _ColorPoints;
        /// <summary>
        /// Počet bodů
        /// </summary>
        public int Count { get { return this._ColorPoints.Count; } }
        /// <summary>
        /// Přidá do ColorBlenderu novou pozici a barvu na ní.
        /// Pokud na téže pozici již je záznam, pak barvu přepíše.
        /// </summary>
        /// <param name="position"></param>
        /// <param name="color"></param>
        public void Add(float position, Color color)
        {
            int index = this._ColorPoints.FindIndex(p => p.Position == position);
            if (index >= 0)
            {   // Pokud pro danou pozici už mám záznam, pak jej aktualizuji (vložím do záznamu novou barvu):
                this._ColorPoints[index].Color = color;
            }
            else
            {   // Dosud neobsazená souřadnice = přidáme nový záznam:
                this._ColorPoints.Add(new ColorBlendPoint(position, color));
                if (this._ColorPoints.Count > 1)
                    this._ColorPoints.Sort(ColorBlendPoint.CompareByPosition);
            }
        }
        /// <summary>
        /// Smaže všechny pozice z ColorBlenderu
        /// </summary>
        public void Clear()
        {
            this._ColorPoints.Clear();
        }
        /// <summary>
        /// Z ColorBlenderu vrátí barvu z dané pozice. Exaktní pozice = vrátí zadanou barvu. Pozice mezi dvěma exaktně zadanými = vrátí aproximovanou barvu.
        /// </summary>
        /// <param name="position"></param>
        /// <returns></returns>
        public Color Get(float position)
        {
            return this.Get(position, Color.Empty);
        }
        /// <summary>
        /// Z ColorBlenderu vrátí barvu z dané pozice. Exaktní pozice = vrátí zadanou barvu. Pozice mezi dvěma exaktně zadanými = vrátí aproximovanou barvu.
        /// </summary>
        /// <param name="position"></param>
        /// <returns></returns>
        public Color Get(float position, Color defaultColor)
        {
            // Exaktní pozice = vrátím exaktní barvu:
            ColorBlendPoint exact = this._ColorPoints.Find(p => p.Position == position);
            if (exact != null) return exact.Color;

            // Aproximace: základem je to, že pole barev je setříděné vzestupně podle Position:
            ColorBlendPoint prev = this._ColorPoints.LastOrDefault(p => p.Position < position);
            ColorBlendPoint next = this._ColorPoints.FirstOrDefault(p => p.Position > position);
            // V následujících případech neproběhne aproximace, protože jsme našli 0 nebo 1 sousední barvu:
            if (prev == null && next == null) return defaultColor;  // Žádná položka => default
            if (prev == null && next != null) return next.Color;    // Máme jen pozici next (její pozice > hledaná pozice, tedy hledám hodně vlevo), vrátím barvu z next
            if (prev != null && next == null) return prev.Color;    //  dtto vpravo.

            // Reálná aproximace (požadovaná pozice leží mezi dvěma reálnými pozicemi, obě jsou not null, pozice je mezi nimi a ne v některé z nich):
            float relative = (position - prev.Position) / (next.Position - prev.Position);        // Relativní pozice 0 - 1, mimo krajní body
            return Color.FromArgb(
                _GetApproximatelly(prev.Color.A, next.Color.A, relative),
                _GetApproximatelly(prev.Color.R, next.Color.R, relative),
                _GetApproximatelly(prev.Color.G, next.Color.G, relative),
                _GetApproximatelly(prev.Color.B, next.Color.B, relative));
        }
        private int _GetApproximatelly(byte a, byte b, float relative)
        {
            float r = (float)a + (relative * ((float)(b - a)));
            if (r < 0F) return 0;
            if (r > 255F) return 255;
            return (int)Math.Round(r, 0);
        }
        /// <summary>
        /// Třída obsahující pozici a její barvu
        /// </summary>
        protected class ColorBlendPoint
        {
            internal ColorBlendPoint(float position, Color color)
            {
                this.Position = position;
                this.Color = color;
            }
            internal static int CompareByPosition(ColorBlendPoint a, ColorBlendPoint b)
            {
                return a.Position.CompareTo(b.Position);
            }
            internal float Position { get; set; }
            internal Color Color { get; set; }
        }
    }
}
